/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.zhiqim.kernel.annotation.AnAlias;
import org.zhiqim.kernel.annotation.AnNullable;
import org.zhiqim.kernel.constants.CodeConstants;
import org.zhiqim.kernel.model.Filter;
import org.zhiqim.kernel.model.Filters;
import org.zhiqim.kernel.model.results.R1;
import org.zhiqim.kernel.model.results.RE;

/**
 * 文件操作工具类
 * 
 * @version v1.0.0 @author zouzhigang 2014-2-27 新建与整理
 */
@AnAlias("Files")
public class Files implements CodeConstants
{
    /*****************************************************************************************/
    //文件目录存在判断
    /*****************************************************************************************/
    
    /** 判断文件是否存在(包括目录和文件) */
    public static boolean exists(File path)
    {
        if (path == null)
            return false;
        
        try
        {
            return path.exists();
        }
        catch(Throwable e)
        {//有安全管理访问的访问异常，返回false;
            return false;
        }
    }
    
    /** 断文件是否存在(包括目录和文件) */
    public static boolean exists(String path)
    {
        return Validates.isEmptyBlank(path)?false:exists(new File(path));
    }
    
    /**
     * 判断目录下文件(包括目录和文件)是否存在
     * 
     * @param dir       目录，目录本身不存在返回false
     * @param name      文件，文件名为空返回false
     * @return          true/false 存在则返回true
     */
    public static boolean exists(File dir, String name)
    {
        if (dir == null || Validates.isEmptyBlank(name))
            return false;
        
        if (!exists(dir))
            return false;
        
        return exists(new File(dir, name));
    }
    
    /** 判断文件是否存在 */
    public static boolean isFile(String path)
    {
        return Validates.isEmptyBlank(path)?false:isFile(new File(path));
    }

    /** 判断文件是否存在 */
    public static boolean isFile(File file)
    {
        return (file == null || !file.exists())?false:file.isFile();
    }
    
    /** 判断路径是否为目录 */
    public static boolean isDirectory(String path)
    {
        return Validates.isEmpty(path)?false:isDirectory(new File(path));
    }
    
    /** 判断路径是否为目录 */
    public static boolean isDirectory(File dir)
    {
        return (dir == null || !dir.exists())?false:dir.isDirectory();
    }
    
    /*****************************************************************************************/
    //文件路径
    /*****************************************************************************************/
    
    /** 把Windows的Path替换成linux的path，即把\替换成/ */
    public static String toLinuxPath(String path)
    {
        return Replaces.replaceAllEscape(path, "\\", "/");
    }
    
    @AnNullable /** 获取文件路径的绝对路径，return null表示异常 */
    public static String getCanonicalPath(String path)
    {
        try
        {
            return new File(path).getCanonicalPath();
        }
        catch (IOException e)
        {
            return null;
        }
    }
    
    /** 获取文件扩展名,则取得文件后缀 注:这里只是找"."，文件后缀,小写 */
    public static String getFileExt(String path)
    {
        if (Validates.isEmptyBlank(path))
            return _EMPTY_;

        path = Strings.trim(path);
        int pos = path.lastIndexOf(".");
        if (pos == -1)
            return _EMPTY_;
        
        String fileExt = path.substring(pos + 1);
        return fileExt.toLowerCase();
    }
    
    /** 对扩展名检查，1)扩展名为空返回空字符串，不为空2)没点号点前面加上点号（.），3)有点号不处理 */
    public static String fixFileExt(String fileExt)
    {
        return Validates.isEmptyBlank(fileExt)?_EMPTY_:Strings.addStartsWith(Strings.trim(fileExt), ".");
    }

    /**
     * 提取一个文件路径的目录结构，不包含最后的分隔符， 如c:\\temp\\article.zml，则返回c:\\temp
     * 
     * @param filePath  文件完整路径
     * @return          目录结构
     */
    public static String getFileDirPath(String filePath)
    {
        String v = getFileDirPath(filePath, Systems.getFileSeparator());
        if (!Validates.isEmpty(v))
            return v;
        
        //如果未找到，再尝试使用默认/方式获到
        return getFileDirPath(filePath, "/");
    }
    
    /** 提取一个文件路径的目录结构，不包含最后的分隔符， 如c:\\temp\\article.zml，则返回c:\\temp */
    public static String getFileDirPath(String filePath, String sep)
    {
        int pos = filePath.lastIndexOf(sep);
        if (pos == -1)
            return _EMPTY_;
        
        return filePath.substring(0, pos);
    }
    
    /** 提取一个文件路径的目录结构，包含最后的分隔符， 如c:\\temp\\article.zml，则返回c:\\temp\\ */
    public static String getFileDir(String filePath)
    {
        String v = getFileDir(filePath, Systems.getFileSeparator());
        if (!Validates.isEmpty(v))
            return v;
        
        //如果未找到，再尝试使用默认/方式获到
        return getFileDir(filePath, "/");
    }
    
    /** 提取一个文件路径的目录结构，包含最后的分隔符， 如c:\\temp\\article.zml，则返回c:\\temp\\ */
    public static String getFileDir(String filePath, String sep)
    {
        int pos = filePath.lastIndexOf(sep);
        if (pos == -1)
            return _EMPTY_;
        
        return filePath.substring(0, pos+sep.length());
    }

    /** 提取一个文件路径的文件名， 如c:\\temp\\article.jsp，则返回article.jsp */
    public static String getFileName(String filePath)
    {
        return getFileName(filePath, Systems.getFileSeparator());
    }
    
    /** 提取一个文件路径的文件名， 如c:\\temp\\article.jsp，则返回article.jsp */
    public static String getFileName(String filePath, String sep)
    {
        int pos = filePath.lastIndexOf(sep);
        if (pos == -1)//未找到，再使用/获取一次
            return getFileNameLinux(filePath);
        else
            return filePath.substring(pos + 1);
    }
    
    /** 提取一个文件路径的文件名，分隔符指定："/"， 如/opt/article.jsp，则返回article.jsp */
    public static String getFileNameLinux(String filePath)
    {
        int pos = filePath.lastIndexOf("/");
        if (pos == -1)
            return filePath;
        else
            return filePath.substring(pos + 1);
    }
    
    /** 获取文件编码 */
    public static String getFileEncoding(String filePath)
    {
        return getFileEncoding(new File(filePath));
    }
    
    /** 获取文件编码 */
    public static String getFileEncoding(File file)
    {
        if (!isFile(file))
            return _UTF_8_;
        
        long len = file.length();
        if (len < 2)
            return _UTF_8_;
        
        int size = len > MiB?MiB:(int)len;
        RandomAccessFile random = randomNewFileR(file).result();
        byte[] b = randomRead(random, 0, size).result();
        return Streams.getStreamEncoding(b, b.length);
    }
    
    /*****************************************************************************************/
    //文件夹操作
    /*****************************************************************************************/
    
    /** 读取目录的所有文件子目录名称列表，=null表示目录不存在或不是目录 */
    public static String[] getDirectoryList(String fileDir)
    {
        Asserts.notEmptyBlank(fileDir);

        File dir = new File(Strings.trim(fileDir));
        if (!dir.exists() || !dir.isDirectory())
            return null;

        return dir.list();
    }
    
    /** 递归创建文件目录 */
    public static boolean mkDirectory(File fileDir)
    {
        if (fileDir.exists())
            return true;
        
        if (!fileDir.getParentFile().exists())
        {
            if (!mkDirectory(fileDir.getParentFile()))
                return false;
        }
        
        return fileDir.mkdir();
    }
    
    /** 递归创建文件目录 */
    public static boolean mkDirectory(String fileDir)
    {
        return mkDirectory(new File(fileDir));
    }
    
    /**
     * 拷贝目录 成功返回true,不是目录或dest路径书写错误返回false
     * 
     * @param src   源目录
     * @param dest  目标目录
     * @return      成功返回true,不是目录或dest路径书写错误返回false
     */
    public static boolean copyDirectory(File src, String dest) throws Exception
    {
        if ((src == null) || !src.isDirectory())
            return false;

        File destRoot = new File(dest);
        if (!destRoot.exists())
            destRoot.mkdir();

        File[] entries = src.listFiles();
        int len = entries.length;
        for (int i = 0; i < len; i++)
        {
            File entry = entries[i];
            String target = dest + Systems.getFileSeparator()+ entry.getName();
            if (entry.isDirectory())
            {
                copyDirectory(entry, target); //递归
            }
            else
            {
                File toFile = new File(target);
                copyFile(entry, toFile);
            }
        }

        return true;
    }
    
    /** 删除文件夹 */
    public static boolean deleteDirectory(String dir)throws Exception
    {
        if (Validates.isEmptyBlank(dir))
            return false;
        
        return deleteDirectory(new File(dir.trim()));
    }
    
    /** 删除文件夹 */
    public static boolean deleteDirectory(File dir)throws Exception
    {
        if (dir == null || !dir.isDirectory())
            return false;
        
        File[] entries = dir.listFiles();
        for (File entry : entries)
        {
            if (entry.isFile())
            {//文件
                entry.delete();
            }
            else
            {//目录递归删除
                deleteDirectory(entry);
            }
        }
        
        dir.delete();
        return true;
    }
    
    /**
     * 删除文件夹
     * 
     * @param dir           文件夹路径
     * @param forbidDirList 禁止删除的目录名称列表
     * @return              true/false
     */
    public static boolean deleteDirectory(String dir, List<String> forbidDirList)throws Exception
    {
        if (Validates.isEmptyBlank(dir))
            return false;

        if (forbidDirList == null)
            return deleteDirectory(new File(dir.trim()));
        
        return deleteDirectory(new File(dir.trim()), forbidDirList);
    }

    /**
     * 删除文件夹
     * 
     * @param dir           文件夹
     * @param forbidDirList 禁止删除的目录名称列表
     * @return true/false
     */
    public static boolean deleteDirectory(File dir, List<String> forbidDirList) throws Exception
    {
        if (dir == null || !dir.isDirectory())
            return false;
        
        if (forbidDirList == null)
            return deleteDirectory(dir);

        boolean isAllowDeleteCurrentDir = true;
        File[] entries = dir.listFiles();
        for (File file : entries)
        {
            if (file.isFile())
            {//文件
                file.delete();
            }
            else
            {//目录且不在禁止目录列表，递归删除
                boolean isAllowDelete = true;
                for (String forbid : forbidDirList)
                {
                    if (Validates.isMatch(file.getName(), forbid))
                    {
                        isAllowDelete = false;
                        isAllowDeleteCurrentDir = false;
                        break;
                    }
                }
                
                if (isAllowDelete)
                {
                    deleteDirectory(file, forbidDirList);
                }
            }
        }

        //最后尝试删除目录，如果目录下没有禁止的则会成功，如果有禁止的是删不了则保留
        if (isAllowDeleteCurrentDir)
            dir.delete();
        
        return true;
    }
    
    /*****************************************************************************************/
    //文件操作
    /*****************************************************************************************/
    
    public static File check(String filePath)
    {
        try
        {
            File file = new File(filePath);
            if (file.exists())
                return file;
            
            String fileDir = file.getParent();
            if (!Files.mkDirectory(fileDir))
                return null;
            
            file.createNewFile();
            return file;
        }
        catch(Throwable e)
        {
            return null;
        }
    }
    
    /** 创建文件，指定文件 */
    public static boolean newFile(String filePath)
    {
        return newFile(new File(filePath));
    }
    
    /** 创建文件，指定文件 */
    public static boolean newFile(File file)
    {
        try
        {
            if (file.exists())
                return true;
            
            String fileDir = file.getParent();
            if (!Files.mkDirectory(fileDir))
                return false;
            
            file.createNewFile();
            return true;
        }
        catch(Throwable e)
        {
            return false;
        }
    }
    
    /**
     * 文件重命名，指定文件重命名到新的文件，类似于文件剪切，要保证目标文件目录存在
     * 
     * @param file      文件对象
     * @param dest      目标文件对象
     * @return          =true表示成功，=false表示失败
     */
    public static boolean renameFile(File file, File dest)
    {
        return file.renameTo(dest);
    }
    
    /**
     * 文件重命名，指定文件重命名到新的文件，类似于文件剪切，要保证目标文件目录存在
     * 
     * @param filePath  文件路径
     * @param destPath  目标文件路径
     * @return          =true表示成功，=false表示失败
     */
    public static boolean renameFile(String filePath, String destPath)
    {
        return renameFile(new File(filePath), new File(destPath));
    }
    
    /**
     * 文件重命名新名称，原目录不变
     * 
     * @param filePath      文件路径
     * @param newFileName   目标文件名
     * @return              =true表示成功，=false表示失败
     */
    public static boolean renameFileName(String filePath, String newFileName)
    {
        String fileDir = getFileDir(filePath);
        String destPath = fileDir + newFileName;
        return renameFile(filePath, destPath);
    }
    
    /**
     * 文件拷贝，逐个字节拷贝到新的文件中件
     * 
     * @param fromFile     源文件对象
     * @param toFile       目标文件对象
     * @return             true/false
     */
    public static boolean copyFile(File fromFile, File toFile) throws Exception
    {
        if (!isFile(fromFile) || toFile == null)
            return false;

        FileInputStream fis = new FileInputStream(fromFile);
        FileOutputStream fos = new FileOutputStream(toFile);

        FileChannel iChannel = fis.getChannel();
        FileChannel oChannel = fos.getChannel();
        FileLock iLock = null;
        FileLock oLock = null;
        
        try
        {
            //先尝试拿原文件共享锁
            iLock = iChannel.tryLock(0L, Long.MAX_VALUE, true);
            if (iLock == null || !iLock.isValid())
                return false;
            
            //再尝试拿目标文件占用锁
            oLock = oChannel.tryLock();
            if (oLock == null || !oLock.isValid())
                return false;
            
            //锁没问题作传输操作
            iChannel.transferTo(0, iChannel.size(), oChannel);
            return true;
        }
        finally
        {
            if (iLock != null){
                iLock.release();
            }
            
            if (oLock != null){
                oLock.release();
            }
            
            Closes.closeIgnoreException(iChannel, oChannel, fis, fos);
        }
    }
    
    /**
     * 文件拷贝类,输入路径
     * 
     * @param fromFile     源文件路径
     * @param toFile       目标文件路径
     * @return             true/false
     */
    public static boolean copyFile(String fromFile, String toFile) throws Exception
    {
        if (Validates.isEmptyBlank(fromFile) || Validates.isEmptyBlank(toFile))
            return false;

        File from = new File(fromFile.trim());
        File to = new File(toFile.trim());

        return copyFile(from, to);
    }

    /**
     * 文件拷贝,输入源文件路径,目标目录,文件名
     * 
     * @param fromFile     源文件路径
     * @param toDir        目标目录
     * @param fileName     目录文件
     * @return             true/false
     */
    public static boolean copyFile(String fromFile, String toDir, String fileName) throws Exception
    {
        if (Validates.isEmptyBlank(fromFile) || Validates.isEmptyBlank(toDir) || Validates.isEmptyBlank(fileName))
            return false;

        File from = new File(fromFile.trim());
        if (!from.exists() || !from.isFile())
            return false;
        
        //新建目录
        toDir = toDir.trim();
        if (!Validates.isEmpty(toDir))
        {
            File dir = new File(toDir);
            if (!dir.exists())
                dir.mkdirs();
            
            dir = null;
        }
        
        if (toDir.lastIndexOf(Systems.getFileSeparator()) != toDir.length())
            toDir += Systems.getFileSeparator();
        
        File to = new File(toDir + fileName.trim());
        return copyFile(from, to);
    }

    /**
     * 删除文件,如果路径为空或文件不存在则返回false
     * 
     * @param filePath     文件完整路径
     * @return             true/false 成功删除则返回true
     */
    public static boolean deleteFile(String filePath)
    {
        if (Validates.isEmptyBlank(filePath))
            return false;

        File file = new File(Strings.trim(filePath));
        if (!file.exists())
            return false;

        return file.delete();
    }
    
    /**
     * 删除文件，并按深度删除父级目录
     * 
     * @param file          文件
     * @param depth         深度值
     * @throws IOException  异常
     */
    public static void deleteFile(File file, int depth) throws IOException
    {
        if (!file.delete() && file.isFile())
            throw new IOException("文件删除失败：" + file);

        File dir = file.getParentFile();
        for (int i=0;i<depth;i++)
        {
            if (!dir.delete())
                break;
            
            dir = dir.getParentFile();
        }
    }
    
    /*****************************************************************************************/
    //读写文件内容
    /*****************************************************************************************/
    
    /**
     * 读取文件内容，并将内容以UTF-8字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     * 
     * @param filePath  文件路径
     * @return          文件内容
     */
    public static String readUTF8(String filePath)
    {
        byte[] bytes = read(filePath);
        return (bytes == null)?null:(bytes.length == 0)?_EMPTY_:Strings.newStringUTF8(bytes);
    }
    
    /**
     * 读取文件内容，并将内容以UTF-8字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     * 
     * @param file  文件对象，最大不超过100M
     * @return      文件内容
     */
    public static String readUTF8(File file)
    {
        byte[] bytes = read(file);
        return (bytes == null)?null:(bytes.length == 0)?_EMPTY_:Strings.newStringUTF8(bytes);
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     * 
     * @param filePath  文件路径，最大不超过100M
     * @param encoding  编码
     * @return          文件内容
     */
    public static String read(String filePath, String encoding)
    {
        byte[] bytes = read(filePath);
        return (bytes == null)?null:(bytes.length == 0)?_EMPTY_:Strings.newString(bytes, encoding);
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     * 
     * @param file      文件对象，最大不超过100M
     * @param encoding  编码
     * @return          文件内容
     */
    public static String read(File file, String encoding)
    {
        byte[] bytes = read(file);
        return (bytes == null)?null:(bytes.length == 0)?_EMPTY_:Strings.newString(bytes, encoding);
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回null
     * 
     * @param filePath  文件路径，最大不超过100M
     * @return          文件内容
     */
    @AnNullable //可能为null，表示文件不存在或不可读，或读时异常
    public static byte[] read(String filePath)
    {
        return read(new File(filePath));
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回null
     * 
     * @param file      文件对象，最大不超过100M
     * @return          文件内容
     */
    @AnNullable //可能为null，表示文件不存在或不可读，或读时异常
    public static byte[] read(File file)
    {
        return read(file, 100 * MiB);
    }
    
    /**
     * 读取文件内容，约定最大值，如指定4096，超过则返回错误
     * 
     * @param file      文件对象
     * @param max       文件长度允许的最大值
     * @return          文件内容
     */
    @AnNullable //可能为null，表示文件不存在或不可读，或读时异常
    public static byte[] read(File file, int max)
    {
        if (file == null || !file.isFile() || !file.canRead())
            return null;
        
        Asserts.as(file.length() <= max?null:"文件长度大于指定的大小[%d]", max);
        
        try(FileInputStream input = new FileInputStream(file))
        {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            Streams.putBytes(input, output);
            return output.toByteArray();
        }
        catch(IOException e)
        {
            return null;
        }
    }
    
    /**
     * 从文件管道中读取指定的长度到缓存中
     * 
     * @param fileChannel       文件管道
     * @param pos               文件起始位置
     * @param buffer            读数据到缓存中
     * @param off               缓存偏移值
     * @param len               读取长度
     * @throws IOException      异常
     */
    public static void read(FileChannel fileChannel, long pos, byte[] buffer, int off, int len) throws IOException
    {
        while (len > 0)
        {
            ByteBuffer buf = ByteBuffer.wrap(buffer, off, len);
            int count = fileChannel.read(buf, pos);
            if (count <= 0)
                throw new EOFException("按长度读文件管道时,长度不够即到达流尾端");
            
            pos += count;
            off += count;
            len -= count;
        }
    }
    
    /**
     * 从文件管道中读取可能的长度到缓存中
     * 
     * @param fileChannel       文件管道
     * @param pos               文件起始位置
     * @param buffer            读数据到缓存中
     * @param off               缓存偏移值
     * @param len               读取长度
     * @throws IOException      异常
     */
    public static int readMaybe(FileChannel fileChannel, long pos, byte[] buffer, int off, int len) throws IOException
    {
        int oLen = len;
        while (len > 0)
        {
            ByteBuffer buf = ByteBuffer.wrap(buffer, off, len);
            int count = fileChannel.read(buf, pos);
            if (count <= 0)
                return oLen - len;
            
            pos += count;
            off += count;
            len -= count;
        }
        
        return oLen;
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     * 
     * @param filePath  文件路径
     * @return          文件内容
     */
    @AnNullable //可能为null，表示文件不存在或不可读，或读时异常
    public static String readGZip(String filePath, String encoding)
    {
        byte[] bytes = read(filePath);
        if (bytes == null || bytes.length == 0)
            return null;
            
        return Zips.unGZipString(bytes, encoding);
    }
    
    /**
     * 按行（去除左右空字符串）读取文件内容，最大值500MiB,UTF8
     * 
     * @param file          文件对象
     * @return              数据列表
     */
    public static List<String> readLineUTF8(File file)
    {
        return readLine(file, _UTF_8_C_, 500 * MiB);
    }
    
    /**
     * 按行（去除左右空字符串）读取文件内容，最大值500MiB,GBK
     * 
     * @param file          文件对象
     * @return              数据列表
     */
    public static List<String> readLineGBK(File file)
    {
        return readLine(file, _GBK_C_, 500 * MiB);
    }
    
    /**
     * 按行（去除左右空字符串）读取文件内容，最大值500MiB,检查文件格式
     * 
     * @param file          文件对象
     * @return              数据列表
     */
    public static List<String> readLine(File file)
    {
        String charset = getFileEncoding(file);
        return readLine(file, Charset.forName(charset), 500 * MiB);
    }
    
    /**
     * 按行（去除左右空字符串）读取文件内容，约定最大值
     * 
     * @param file          文件对象
     * @param charset       字符集
     * @param max           文件长度允许的最大值
     * @return              数据列表
     */
    public static List<String> readLine(File file, Charset charset, int max)
    {
        Asserts.as((file != null && file.isFile() && file.canRead())?null:"文件不存在或不可读");
        Asserts.as(file.length() <= max?null:"文件长度大于指定的大小[%d]", max);
        
        List<String> list = new ArrayList<>();
        try(FileInputStream in = new FileInputStream(file))
        {
            String line = null;
            BufferedReader reader = Streams.toReader(in, charset);
            while ((line = reader.readLine()) != null)
            {
                list.add(Strings.trim(line));
            }
            
            return list;
        }
        catch (IOException e)
        {
            throw Asserts.exception(e);
        }
    }

    
    /**
     * 根据文件路径,名称,把内容以字节数组的方式写入到指定的文件中
     * 
     * @param filePath  文件路径
     * @param input     文件流，方法内不会关闭input
     * @return          true/false 写入操作成功，返回true
     */
    public static long writeReturnSize(String filePath, InputStream input)
    {
        //1.检查参数
        Asserts.notEmptyBlank(filePath, "filePath");
        Asserts.notNull(input, "input");
        
        
        //2.检查文件目录
        String fileDirPath = getFileDirPath(filePath);
        Asserts.as(mkDirectory(fileDirPath)?null:"写入文件时["+filePath+"]目录创建失败");
        
        //3.写入到文件中
        File file = new File(filePath);
        try (FileOutputStream output = new FileOutputStream(file))
        {
            Streams.putBytes(input, output);
        }
        catch (Exception e)
        {
            throw Asserts.exception("写入文件时["+filePath+"]，异常", e);
        }
        
        //4.返回长度
        return file.length();
    }
    
    /**
     * 根据文件路径，写入到指定的文件中
     * 
     * @param filePath  文件完整路径
     * @param bytes     文件内容
     * @return          true/false 写入操作成功，返回true
     */
    public static long writeReturnSize(String filePath, byte[] bytes)
    {
        //1.检查参数
        Asserts.notEmptyBlank(filePath, "filePath");
        Asserts.notNull(bytes, "bytes");

        //2.检查文件目录
        String fileDirPath = getFileDirPath(filePath);
        Asserts.as(mkDirectory(fileDirPath)?null:"写入文件时["+filePath+"]目录创建失败");
        
        //3.写入到文件中
        File file = new File(filePath);
        try (FileOutputStream fos = new FileOutputStream(file))
        {
            fos.write(bytes);
            fos.flush();
        }
        catch (Exception e)
        {
            throw Asserts.exception("写入文件时["+filePath+"]，异常", e);
        }
        
        //4.返回长度
        return file.length();
    }

    /**
     * 根据文件路径,名称,把内容以字节数组的方式写入到指定的文件中
     * 
     * @param filePath  文件完整路径
     * @param bytes     文件内容
     * @return          true/false 写入操作成功，返回true
     */
    public static boolean write(String filePath, byte[] bytes)
    {
        //1.检查参数
        Asserts.notEmptyBlank(filePath, "filePath");
        Asserts.notNull(bytes, "bytes");

        //2.检查文件目录
        String fileDirPath = getFileDirPath(filePath);
        Asserts.as(mkDirectory(fileDirPath)?null:"写入文件时["+filePath+"]目录创建失败");
        
        //3.写入到文件中
        try (FileOutputStream fos = new FileOutputStream(filePath))
        {
            fos.write(bytes);
            fos.flush();
            return true;
        }
        catch (IOException ex)
        {
            return false;
        }
    }
    
    /**
     * 根据文件路径,名称,把内容以字节数组的方式写入到指定的文件中
     * 
     * @param filePath  文件完整路径
     * @param bytes     文件内容
     * @param append    是否追加
     * @return          true/false 写入操作成功，返回true
     */
    public static boolean write(File file, byte[] bytes, boolean append)
    {
        //1.检查参数
        Asserts.notNull(file, "file");
        Asserts.notNull(bytes, "bytes");

        //2.检查文件目录
        Asserts.as(mkDirectory(file.getParentFile())?null:"写入文件时["+file+"]目录创建失败");
        
        //3.写入到文件中
        try (FileOutputStream fos = new FileOutputStream(file, append))
        {
            fos.write(bytes);
            fos.flush();
            return true;
        }
        catch (IOException ex)
        {
            return false;
        }
    }

    /**
     * 根据文件路径,名称,把内容以流的方式写入到指定的文件中
     * 
     * @param filePath  文件完整路径
     * @param content   文件内容
     * @param encoding  编码
     * @return          true/false 写入操作成功，返回true
     */
    public static boolean write(String filePath, String content, String encoding)
    {
        try
        {
            return write(filePath, content.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e)
        {
            return false;
        }
    }

    /**
     * 根据文件路径，指定UTF-8编码写入内容到文件
     * 
     * @param filePath  文件完整路径
     * @param content   文件内容
     * @return          true/false 写入操作成功，返回true
     */
    public static boolean writeUTF8(String filePath, String content)
    {
        return write(filePath, content, _UTF_8_);
    }
    
    /**
     * 根据文件路径，指定UTF-8编码写入内容到文件
     * 
     * @param file      文件完整路径
     * @param content   文件内容
     * @param append    是否追加
     * @return          true/false 写入操作成功，返回true
     */
    public static boolean writeUTF8(File file, String content, boolean append)
    {
        Asserts.notNull(content, "content");
        
        return write(file, content.getBytes(_UTF_8_C_), append);
    }
    
    /*****************************************************************************************/
    //文件搜索
    /*****************************************************************************************/
    
    
    /**
     * 递归初始化文件列表，包含子目录
     * 
     * @param fileList  传入的文件列表引用，需先new，不允许为null;
     * @param dir       指定搜索目录
     * @param type      类型，0表示目录和文件，1表示文件，2表示目录
     */
    public static void queryFileList(List<File> fileList, File dir, int type)
    {
        File[] list = dir.listFiles();
        for (File file : list)
        {
            if (file.isFile())
            {
                if (type == 0 || type == 1)
                    fileList.add(file);
            }
            else
            {
                if (type == 0 || type == 2)
                    fileList.add(file);
                queryFileList(fileList, file, type);
            }
        }
    }

    /** 
     * 递归初始化文件列表 
     * 
     * @param fileList  传入的文件列表引用，需先new，不允许为null;
     * @param dir       指定搜索目录
     * @param filter    过滤器
     */
    public static void queryFilterList(List<File> fileList, File dir, Filter filter)
    {
        File[] list = dir.listFiles();
        for (int i=0;i<list.length;i++)
        {
            File file = list[i];
            if (file.isFile())
            {
                if (filter.accept(file.getName()))
                    fileList.add(file);
            }
            else
            {
                queryFilterList(fileList, file, filter);
            }
        }
    }
    
    /** 
     * 递归初始化文件列表 
     * 
     * @param fileList  传入的文件列表引用，需先new，不允许为null;
     * @param dir       指定搜索目录
     * @param endsWith  后缀或以该结尾相同的数据,调用endsWith进行匹配，endsWith为null或空时表示全匹配
     */
    public static void queryEndsWithList(List<File> fileList, File dir, String endsWith)
    {
        queryFilterList(fileList, dir, new Filters.EndsWithName(endsWith));
    }
    
    /** 
     * 递归初始化文件列表 
     * 
     * @param fileNameList  传入的文件列表引用，需先new，不允许为null;
     * @param dir           指定搜索目录
     * @param filter        过滤器
     */
    public static void queryFilterNameList(List<String> fileNameList, File dir, Filter filter)
    {
        File[] list = dir.listFiles();
        for (int i=0;i<list.length;i++)
        {
            File file = list[i];
            if (file.isFile())
            {
                if (filter.accept(file.getName()))
                    fileNameList.add(file.getName());
            }
            else
            {
                queryFilterNameList(fileNameList, file, filter);
            }
        }
    }
    
    /** 
     * 递归初始化文件列表 
     * 
     * @param fileNameList  传入的文件列表引用，需先new，不允许为null;
     * @param dir           指定搜索目录
     * @param endsWith      后缀或以该结尾相同的数据,调用endsWith进行匹配，endsWith为null或空时表示全匹配
     */
    public static void queryEndsWithNameList(List<String> fileNameList, File dir, String endsWith)
    {
        queryFilterNameList(fileNameList, dir, new Filters.EndsWithName(endsWith));
    }
    
    /*****************************************************************************************/
    //类文件路径取类名
    /*****************************************************************************************/
    
    /**
     * 通过类文件路径和类路径获取类名，异常返回null
     * 
     * @param classPath         类路径
     * @param classFile         类文件路径
     * @return                  类名
     */
    public static String getClassName(File classPath, File classFile)
    {
        try
        {
            String classPathDir = classPath.getCanonicalPath();
            String classFilePath = classFile.getCanonicalPath();
            
            return getClassName(classPathDir, classFilePath);
        }
        catch (IOException e)
        {
            return null;
        }
    }
    
    /**
     * 通过类文件路径和类路径获取类名
     * 
     * @param classPath         类路径
     * @param classFilePath     类文件路径
     * @return                  类名
     */
    public static String getClassName(String classPath, String classFilePath)
    {//格式如：d:\zhiqim\bin\org\zhiqim\kernel\Global.class
     //1.去除目录前缀,2.去除.class的后缀,3.把文件分隔符替换成.,4.把前面的.去掉
        String className = Strings.trimLeft(classFilePath, classPath);
        className = Strings.trimRight(className, ".class");
        className = Replaces.replaceAllEscape(className, Systems.getFileSeparator(), ".");
        className = Strings.trimLeft(className, ".");
        return className;
    }
    
    /*****************************************************************************************/
    //根据日期生成文件路径
    /*****************************************************************************************/
    
    /**
     * 生成文件目录路径，以当前年月日组成一个路径，结果如UNIX:2012/3/2,2012/11/24,WIN:2012\3\\2,2012\\11\\2
     * 
     * @return String 完整路径
     */
    public static String getPathByCurrentDate()
    {
        Calendar date = Calendar.getInstance();
        int year = date.get(Calendar.YEAR);
        int month = date.get(Calendar.MONTH) + 1;
        int day = date.get(Calendar.DATE);

        return year + Systems.getFileSeparator() + month + Systems.getFileSeparator() + day;
    }

    /**
     * 生成目录路径，以指定日期组成一个路径，结果如UNIX:2012/3/2,2012/11/24,WIN:2012\3\\2,2012\\11\\2
     * 
     * @return String 完整路径
     */
    public static String getPathByCurrentDate(Date date)
    {
        Calendar calender = Calendar.getInstance();
        calender.setTime(date);

        int year = calender.get(Calendar.YEAR);
        int month = calender.get(Calendar.MONTH) + 1;
        int day = calender.get(Calendar.DATE);

        return year + Systems.getFileSeparator() + month + Systems.getFileSeparator() + day;
    }
    
    /**
     * 删除多余的日志文件并重命名fileName,fileName.1,fileName.2,fileName.3成fileName.1,fileName.2,fileName.3,fileName.4
     * 
     * @param fileDir       文件目录
     * @param fileName      文件名称
     * @param fileMaxNum    文件最大个数
     * @param renameCurFile 是否重命令当前文件成fileName.1
     */
    public static void removeMaxFileAndRenameFile(String fileDir, String fileName, int fileMaxNum, boolean renameCurrentFile)
    {
        if (fileMaxNum <= 0)
            return;

        fileDir = toLinuxPath(fileDir);
        fileDir = Strings.removeEndsWith(fileDir, "/");
        
        //把>=fileMaxNum文件名的都删除，<fileMaxNum的都改名后缀加1
        HashMap<Integer, File> renameMap = new HashMap<Integer, File>();
        
        File dir = new File(fileDir);
        for (File file : dir.listFiles()) 
        {
            if (!file.isFile() || !file.getName().startsWith(fileName+"."))
                continue;
            
            String index = file.getName().substring(fileName.length()+1);
            if (!Validates.isInteger(index))
                continue;
                
            int ind = Ints.toInt(index);
            if (ind >= fileMaxNum)
                file.delete();
            else
                renameMap.put(ind, file);
        }
        
        //小于fileMaxNum的文件改名。从大到小重命名 .1,.2,.3成.2,.3,.4
        String filePath = fileDir + "/" + fileName;
        for (int i=fileMaxNum-1;i>=1;i--)
        {
            File file = renameMap.get(i);
            if (file == null)
                continue;
            
            file.renameTo(new File(filePath + "." + (i+1)));
        }
        
        //最后重命名当前文件为.1
        if (renameCurrentFile && Files.exists(filePath))
        {
            new File(filePath).renameTo(new File(filePath + ".1"));
        }
    }
    
    /*****************************************************************************************/
    //文件系统
    /*****************************************************************************************/
    
    /**
     * 判断文件是否有执行权限，Windows下返回false，其他系统判断
     * 
     * @param file      文件
     * @return          =true表示有，=false表示无权限
     */
    public static boolean canExecute(File file)
    {
        return Systems.isWindows()?false:file.canExecute();
    }

    /**
     * 设置文件增加或删除执行权限，Windows下无效，POSIX下适用
     * 
     * @param file          文件
     * @param executable    是否可执行
     * @return              =true表示设置成功，=false表示设置失败
     */
    public static boolean setExecutable(File file, boolean executable)
    {
        return Systems.isWindows()?false:file.setExecutable(executable);
    }
    
    /** 设置文件/目录为隐藏属性，仅window有效 */
    public static void setHidden(String path, boolean hidden)
    {
        setHidden(new File(path), hidden);
    }
    
    /** 设置文件/目录为隐藏属性，仅window有效 */
    public static void setHidden(File file, boolean hidden)
    {
        if (!Systems.isWindows() || !exists(file))
            return;
        
        try
        {
            //attrib +H "c:\abc.text"
            String cmd = new StringBuilder()
                .append("attrib ").append(hidden?"+":"-").append("H ")
                .append("\"").append(file.getCanonicalPath()).append("\"")
                .toString();
            
            Windows.cmdNotResult(cmd);
        }
        catch (IOException e)
        {
            throw Asserts.exception(e);
        } 
    }
    
    /*****************************************************************************************/
    //随机访问读写文件
    /*****************************************************************************************/
    
    /** 新建随机访问文件，异常时返回null */
    public static RE<RandomAccessFile> randomNewFile(File file, String mode)
    {
        try{return RE.value(new RandomAccessFile(file, mode));}catch (Throwable e){return RE.error(e);}
    }
    
    /** 新建随机访问只读文件，异常时返回null */
    public static RE<RandomAccessFile> randomNewFileR(File file)
    {
        try{return RE.value(new RandomAccessFile(file, _RO_));}catch (Throwable e){return RE.error(e);}
    }
    
    /** 新建随机访问读写文件，异常时返回null */
    public static RE<RandomAccessFile> randomNewFileRW(File file)
    {
        try{return RE.value(new RandomAccessFile(file, _RW_));}catch (Throwable e){return RE.error(e);}
    }
    
    /** 新建随机访问文件，异常时返回null */
    public static RE<RandomAccessFile> randomNewFile(String filePath, String mode)
    {
        try{return RE.value(new RandomAccessFile(filePath, mode));}catch (Throwable e){return RE.error(e);}
    }
    
    /** 新建随机访问只读文件，异常时返回null */
    public static RE<RandomAccessFile> randomNewFileR(String filePath)
    {
        try{return RE.value(new RandomAccessFile(filePath, _RO_));}catch (Throwable e){return RE.error(e);}
    }
    
    /** 新建随机访问读写文件，异常时返回null */
    public static RE<RandomAccessFile> randomNewRW(String filePath)
    {
        try{return RE.value(new RandomAccessFile(filePath, _RW_));}catch (Throwable e){return RE.error(e);}
    }
    
    /** 关闭随机访问文件 */
    public static void randomClose(RandomAccessFile file)
    {
        if (file == null)
            return;
        
        try{file.close();}catch (Throwable e){}
    }
    
    /** 读取文件指定位置数据 */
    public static RE<byte[]> randomRead(RandomAccessFile file, long pos, int len)
    {
        try
        {
            byte[] b = new byte[len];
            file.seek(pos);
            file.read(b);
            
            return RE.value(b);
        }
        catch (Throwable e)
        {
            return RE.error(e);
        }
    }
    
    /** 读取文件数据 */
    public static RE<String> randomReadUTF8(RandomAccessFile file, long pos, int len)
    {
        RE<byte[]> result = randomRead(file, pos, len);
        if (result.failure())
            return RE.error(result.exception());
        
        return RE.value(new String(result.value(), _UTF_8_C_));
    }
    
    /** 写入字节数据到文件指定位置 */
    public static R1 randomWrite(RandomAccessFile file, long pos, byte[] b)
    {
        try
        {
            file.seek(pos);
            file.write(b);
            return R1.correct();
        }
        catch (Throwable e)
        {
            return R1.error(e);
        }
    }
    
    /** 写入字符串到文件指定位置 */
    public static R1 randomWriteUTF8(RandomAccessFile file, long pos, String content)
    {
        try
        {
            file.seek(pos);
            file.write(content.getBytes(_UTF_8_));
            return R1.correct();
        }
        catch (Throwable e)
        {
            return R1.error(e);
        }
    }
}